package com.yc.sole.system.binlog;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.db.Db;
import cn.hutool.db.Entity;
import com.github.shyiko.mysql.binlog.BinaryLogClient;
import com.github.shyiko.mysql.binlog.event.*;
import com.yc.sole.system.dto.BizLogDto;
import com.yc.sole.system.repository.BizLogRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author yizuomin
 * @date 2024/3/20 13:26
 **/
@Slf4j
@Component
@RequiredArgsConstructor
public class BinlogListener implements CommandLineRunner {

    private static final String SQL = "SELECT COLUMN_NAME name, COLUMN_COMMENT comment,DATA_TYPE TYPE ,ORDINAL_POSITION sort " +
            "FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ''{0}'' AND TABLE_NAME = ''{1}'' ORDER BY ORDINAL_POSITION ;";
    private final BizLogRepository bizLogRepository;
    private final DataSource dataSource;
    private final Map<Long, String> TABLE_MAP = new HashMap<>();
    private final Map<Long, List<ColumnInfo>> TABLE_COLUMN_MAP = new HashMap<>();
    private final BizLogConfig bizLogConfig;
    private static final String CREATE_USER_ID = "create_user_id";
    private static final String UPDATE_USER_ID = "update_user_id";
    private static final String ID = "id";

    @Override
    @Async("async_thread_pool")
    public void run(String... args) throws Exception {

        log.info("业务日志初始化成功：{}", bizLogConfig.toString());

        BinaryLogClient client = new BinaryLogClient(bizLogConfig.getHost(), bizLogConfig.getPort(), bizLogConfig.getUsername(), bizLogConfig.getPassword());
        client.setServerId(1);
        client.registerEventListener(event -> {
            EventData data = event.getData();
            if (data instanceof TableMapEventData) {
                // 初始化表结构信息
                TableMapEventData tableMapEventData = (TableMapEventData) data;
                this.initTableInfo(tableMapEventData);
            }
            if (data instanceof UpdateRowsEventData) {
                UpdateRowsEventData data1 = (UpdateRowsEventData) data;
                this.handleTableUpdate(data1);
            } else if (data instanceof WriteRowsEventData) {
                WriteRowsEventData data1 = (WriteRowsEventData) data;
                this.handleTableInsert(data1);
            } else if (data instanceof DeleteRowsEventData) {
                // 删除不用记录日志
            }
        });

        try {
            client.connect();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void initTableInfo(TableMapEventData tableMapEventData) {
        String tableName = tableMapEventData.getTable();
        if (!bizLogConfig.getTables().contains(tableName)) {
            return;
        }
        Db use = Db.use(dataSource);
        String database = tableMapEventData.getDatabase();
        long tableId = tableMapEventData.getTableId();
        TABLE_MAP.put(tableId, tableName);
        String formatSql = MessageFormat.format(SQL, database, tableName);
        List<ColumnInfo> columnInfoList = TABLE_COLUMN_MAP.get(tableId);
        if (CollUtil.isEmpty(columnInfoList)) {
            columnInfoList = new ArrayList<>();
            try {
                List<Entity> query = use.query(formatSql);
                for (Entity entity : query) {
                    ColumnInfo tableInfo = entity.toBean(ColumnInfo.class);
                    columnInfoList.add(tableInfo);
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        TABLE_COLUMN_MAP.put(tableId, columnInfoList);
        log.info("表:{},ID:{},结构初始化成功!", tableName, tableId);
    }

    private void handleTableInsert(WriteRowsEventData data) {
        long tableId = data.getTableId();
        String tableName = TABLE_MAP.get(tableId);

        if (!bizLogConfig.getTables().contains(tableName)) {
            return;
        }

        List<ColumnInfo> columnInfoList = TABLE_COLUMN_MAP.get(tableId);
        ColumnInfo id = columnInfoList.stream().filter(t -> ID.equals(t.getName())).findFirst().get();
        int idIndex = columnInfoList.indexOf(id);

        ColumnInfo createUserId = columnInfoList.stream().filter(t -> CREATE_USER_ID.equals(t.getName())).findFirst().get();
        int createUserIdIndex = columnInfoList.indexOf(createUserId);

        for (int i = 0; i < data.getRows().size(); i++) {
            Serializable[] serializables = data.getRows().get(i);

            Serializable idValue = serializables[idIndex];
            Serializable updateUserIdValue = serializables[createUserIdIndex];
            Long userId = Long.valueOf(updateUserIdValue.toString());
            Long bizId = Long.valueOf(idValue.toString());

            BizLogDto bizLogDto = new BizLogDto();
            bizLogDto.setTableName(tableName);
            bizLogDto.setBizId(bizId);
            bizLogDto.setUserId(userId);
            bizLogDto.setDetails(null);
            bizLogDto.setOperationType("创建");
            bizLogDto.setCreateTime(DateUtil.now());
            bizLogRepository.save(bizLogDto);
        }

    }

    private void handleTableUpdate(UpdateRowsEventData data) {
        long tableId = data.getTableId();
        String tableName = TABLE_MAP.get(tableId);

        if (!bizLogConfig.getTables().contains(tableName)) {
            return;
        }

        List<ColumnInfo> columnInfoList = TABLE_COLUMN_MAP.get(tableId);
        ColumnInfo id = columnInfoList.stream().filter(t -> ID.equals(t.getName())).findFirst().get();
        int idIndex = columnInfoList.indexOf(id);

        ColumnInfo updateUserId = columnInfoList.stream().filter(t -> UPDATE_USER_ID.equals(t.getName())).findFirst().get();
        int updateUserIdIndex = columnInfoList.indexOf(updateUserId);

        Map<String, String> dictColumnMap = bizLogConfig.getEnumColumns();

        for (int i = 0; i < data.getRows().size(); i++) {
            List<String> messageList = new ArrayList<>();
            Map.Entry<Serializable[], Serializable[]> entry = data.getRows().get(i);
            Serializable[] before = entry.getKey();
            Serializable[] after = entry.getValue();
            Serializable idValue = after[idIndex];
            Serializable updateUserIdValue = after[updateUserIdIndex];


            Long userId = Long.valueOf(updateUserIdValue.toString());
            Long bizId = Long.valueOf(idValue.toString());
            for (int j = 0; j < entry.getKey().length; j++) {
                Serializable beforeValue = before[j];
                Serializable afterValue = after[j];
                beforeValue = beforeValue == null ? "空" : beforeValue;
                afterValue = afterValue == null ? "空" : afterValue;

                ColumnInfo updateColumn = columnInfoList.get(j);
                if (bizLogConfig.getColumnWhiteList().contains(updateColumn.getName())) {
                    continue;
                }
                String enumClass = dictColumnMap.get(updateColumn.getName());

                if (beforeValue.equals(afterValue)) {
                    continue;
                }
                if (enumClass != null) {
                    Class<?> aClass = null;
                    try {
                        aClass = Class.forName(enumClass);
                        for (Object enumConstant : aClass.getEnumConstants()) {
                            Method getValue = aClass.getMethod("getValue");
                            Method getDescription = aClass.getMethod("getDescription");
                            Object value = getValue.invoke(enumConstant);
                            Object description = getDescription.invoke(enumConstant);

                            String string1 = beforeValue.toString();
                            String string2 = afterValue.toString();
                            if (value.equals(string1)) {
                                beforeValue = description.toString();
                            }
                            if (value.equals(string2)) {
                                afterValue = description.toString();
                            }
                        }
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
                String msg = "{0} 由 {1} 变更为 {2}";
                String format = MessageFormat.format(msg, updateColumn.getComment(), beforeValue, afterValue);
                messageList.add(format);
            }
            if (CollUtil.isNotEmpty(messageList)) {
                String details = CharSequenceUtil.join(",", messageList);
                BizLogDto bizLogDto = new BizLogDto();
                bizLogDto.setOperationType("修改");
                bizLogDto.setTableName(tableName);
                bizLogDto.setBizId(bizId);
                bizLogDto.setUserId(userId);
                bizLogDto.setDetails(details);
                bizLogDto.setCreateTime(DateUtil.now());
                bizLogRepository.save(bizLogDto);
            }
        }
    }
}
